home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power Programmierung
/
Power-Programmierung (Tewi)(1994).iso
/
magazine
/
drdobbs
/
1991
/
11
/
grphprog.asc
< prev
next >
Wrap
Text File
|
1991-10-18
|
26KB
|
579 lines
_GRAPHICS PROGRAMMING COLUMN_
by Michael Abrash
[LISTING ONE]
/* Looks for a Sierra Hicolor DAC; if one is present, puts the VGA into the
specified Hicolor (32K color) mode. Relies on the Tseng Labs ET4000 BIOS and
hardware; probably will not work on adapters built around other VGA chips.
Returns 1 for success, 0 for failure; failure can result from no Hicolor DAC,
too little display memory, or lack of an ET4000. Tested with Borland C++ 2.
0 in C mode in the small model. */
#include <dos.h>
#define DAC_MASK 0x3C6 /* DAC pixel mask reg address, also Sierra
command reg address when enabled */
#define DAC_WADDR 0x3C8 /* DAC write address reg address */
/* Mode selections: 0x2D=640x350; 0x2E=640x480; 0x2F=640x400; 0x30=800x600 */
int SetHCMode(int Mode) {
int i, Temp1, Temp2, Temp3;
union REGS regset;
/* See if a Sierra SC1148X Hicolor DAC is present, by trying to
program and then read back the DAC's command register. (Shouldn't be
necessary when using the BIOS Get DAC Type function, but the BIOS function
locks up some computers, so it's safer to check the hardware first) */
inp(DAC_WADDR); /* reset the Sierra command reg enable sequence */
for (i=0; i<4; i++) inp(DAC_MASK); /* enable command reg access */
outp(DAC_MASK, 0x00); /* set command reg (if present) to 0x00, and
reset command reg enable sequence */
outp(DAC_MASK, 0xFF); /* command reg access no longer enabled;
set pixel mask register to 0xFF */
for (i=0; i<4; i++) inp(DAC_MASK); /* enable command reg access */
/* If this is a Hicolor DAC, we should read back the 0 in the
command reg; otherwise we get the 0xFF in the pixel mask reg */
i = inp(DAC_MASK); inp(DAC_WADDR); /* reset enable sequence */
if (i == 0xFF) return(0);
/* Check for a Tseng Labs ET4000 by poking unique regs, (assumes
VGA configured for color, w/CRTC addressing at 3D4/5) */
outp(0x3BF, 3); outp(0x3D8, 0xA0); /* unlock extended registers */
/* Try toggling AC R16 bit 4 and seeing if it takes */
inp(0x3DA); outp(0x3C0, 0x16 | 0x20);
outp(0x3C0, ((Temp1 = inp(0x3C1)) | 0x10)); Temp2 = inp(0x3C1);
outp(0x3C0, 0x16 | 0x20); outp(0x3C0, (inp(0x3C1) & ~0x10));
Temp3 = inp(0x3C1); outp(0x3C0, 0x16 | 0x20);
outp(0x3C0, Temp1); /* restore original AC R16 setting */
/* See if the bit toggled; if so, it's an ET3000 or ET4000 */
if ((Temp3 & 0x10) || !(Temp2 & 0x10)) return(0);
outp(0x3D4, 0x33); Temp1 = inp(0x3D5); /* get CRTC R33 setting */
outp(0x3D5, 0x0A); Temp2 = inp(0x3D5); /* try writing to CRTC */
outp(0x3D5, 0x05); Temp3 = inp(0x3D5); /* R33 */
outp(0x3D5, Temp1); /* restore original CRTC R33 setting */
/* If the register was writable, it's an ET4000 */
if ((Temp3 != 0x05) || (Temp2 != 0x0A)) return(0);
/* See if a Sierra SC1148X Hicolor DAC is present by querying the
(presumably) ET4000-compatible BIOS. Not really necessary after
the hardware check above, but generally more useful; in the
future it will return information about other high-color DACs */
regset.x.ax = 0x10F1; /* Get DAC Type BIOS function # */
int86(0x10, ®set, ®set); /* ask BIOS for the DAC type */
if (regset.x.ax != 0x0010) return(0); /* function not supported */
switch (regset.h.bl) {
case 0: return(0); /* normal DAC (non-Hicolor) */
case 1: break; /* Sierra SC1148X 15-bpp Hicolor DAC */
default: return(0); /* other high-color DAC */
}
/* Set Hicolor mode */
regset.x.ax = 0x10F0; /* Set High-Color Mode BIOS function # */
regset.h.bl = Mode; /* desired resolution */
int86(0x10, ®set, ®set); /* have BIOS enable Hicolor mode */
return (regset.x.ax == 0x0010); /* 1 for success, 0 for failure */
}
[LISTING TWO]
/* Demonstrates non-antialiased drawing in 640x480 Hicolor (32K color) mode on
an ET4000-based SuperVGA with a Sierra Hicolor DAC installed. Tested with
Borland C++ 2.0 in C mode in the small model. */
#include <conio.h>
#include <dos.h>
#include "polygon.h"
/* Draws the polygon described by the point list PointList in color
Color, with all vertices offset by (x,y) */
#define DRAW_POLYGON(PointList,Color,x,y) { \
Polygon.Length = sizeof(PointList)/sizeof(struct Point); \
Polygon.PointPtr = PointList; \
FillCnvxPolyDrvr(&Polygon, Color, x, y, DrawHCLineList);}
void main(void);
extern int SetHCMode(int);
extern int FillCnvxPolyDrvr(struct PointListHeader *, int, int, int,
void (*)());
extern void DrawHCLineList(struct HLineList *, int);
int BitmapWidthInBytes = 640*2; /* # of bytes per raster line */
void main()
{
struct PointListHeader Polygon;
static struct Point Face0[] = {{396,276},{422,178},{338,88},{288,178}};
static struct Point Face1[] = {{306,300},{396,276},{288,178},{210,226}};
static struct Point Face2[] = {{338,88},{266,146},{210,226},{288,178}};
union REGS regset;
/* Attempt to enable 640x480 Hicolor mode */
if (SetHCMode(0x2E) == 0)
{ printf("No Hicolor DAC detected\n"); exit(0); };
/* Draw the cube */
DRAW_POLYGON(Face0, 0x1F, 0, 0); /* full-intensity blue */
DRAW_POLYGON(Face1, 0x1F << 5, 0, 0); /* full-intensity green */
DRAW_POLYGON(Face2, 0x1F << 10, 0, 0); /* full-intensity red */
getch(); /* wait for a keypress */
/* Return to text mode and exit */
regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */
int86(0x10, ®set, ®set);
}
[LISTING THREE]
/* Draws all pixels in the list of horizontal lines passed in, in Hicolor
(32K color) mode on an ET4000-based SuperVGA. Uses a slow pixel-by-pixel
approach. Tested with Borland C++ 2.0 in C mode in the small model. */
#include <dos.h>
#include "polygon.h"
#define SCREEN_SEGMENT 0xA000
#define GC_SEGMENT_SELECT 0x3CD
void DrawPixel(int, int, int);
extern int BitmapWidthInBytes; /* # of pixels per line */
void DrawHCLineList(struct HLineList * HLineListPtr,
int Color)
{
struct HLine *HLinePtr;
int Y, X;
/* Point to XStart/XEnd descriptor for the first (top) horizontal line */
HLinePtr = HLineListPtr->HLinePtr;
/* Draw each horizontal line in turn, starting with the top one and
advancing one line each time */
for (Y = HLineListPtr->YStart; Y < (HLineListPtr->YStart +
HLineListPtr->Length); Y++, HLinePtr++) {
/* Draw each pixel in the current horizontal line in turn,
starting with the leftmost one */
for (X = HLinePtr->XStart; X <= HLinePtr->XEnd; X++)
DrawPixel(X, Y, Color);
}
}
/* Draws the pixel at (X, Y) in color Color in Hicolor mode on an
ET4000-based SuperVGA */
void DrawPixel(int X, int Y, int Color) {
unsigned int far *ScreenPtr, Bank;
unsigned long BitmapAddress;
/* Full bitmap address of pixel, as measured from address 0 to
address 0xFFFFF. (X << 1) because pixels are 2 bytes in size */
BitmapAddress = (unsigned long) Y * BitmapWidthInBytes + (X << 1);
/* Map in the proper bank. Bank # is upper word of bitmap addr */
Bank = *(((unsigned int *)&BitmapAddress) + 1);
/* Upper nibble is read bank #, lower nibble is write bank # */
outp(GC_SEGMENT_SELECT, (Bank << 4) | Bank);
/* Draw into the bank */
FP_SEG(ScreenPtr) = SCREEN_SEGMENT;
FP_OFF(ScreenPtr) = *((unsigned int *)&BitmapAddress);
*ScreenPtr = (unsigned int)Color;
}
[LISTING FOUR]
; Draws all pixels in the list of horizontal lines passed in, in
; Hicolor (32K color) mode on an ET4000-based SuperVGA. Uses REP STOSW
; to fill each line. Tested with TASM 2.0. C near-callable as:
; void DrawHCLineList(struct HLineList * HLineListPtr, int Color);
SCREEN_SEGMENT equ 0a000h
GC_SEGMENT_SELECT equ 03cdh
HLine struc
XStart dw ? ;X coordinate of leftmost pixel in line
XEnd dw ? ;X coordinate of rightmost pixel in line
HLine ends
HLineList struc
Lngth dw ? ;# of horizontal lines
YStart dw ? ;Y coordinate of topmost line
HLinePtr dw ? ;pointer to list of horz lines
HLineList ends
Parms struc
dw 2 dup(?) ;return address & pushed BP
HLineListPtr dw ? ;pointer to HLineList structure
Color dw ? ;color with which to fill
Parms ends
; Advances both the read and write windows to the next 64K bank.
; Note: Theoretically, a delay between IN and OUT may be needed under
; some circumstances to avoid accessing the VGA chip too quickly, but
; in actual practice, I haven't found any delay to be required.
INCREMENT_BANK macro
push ax ;preserve fill color
push dx ;preserve scan line start pointer
mov dx,GC_SEGMENT_SELECT
in al,dx ;get the current segment select
add al,11h ;increment both the read & write banks
out dx,al ;set the new bank #
pop dx ;restore scan line start pointer
pop ax ;restore fill color
endm
.model small
.data
extrn _BitmapWidthInBytes:word
.code
public _DrawHCLineList
align 2
_DrawHCLineList proc near
push bp ;preserve caller's stack frame
mov bp,sp ;point to our stack frame
push si ;preserve caller's register variables
push di
cld ;make string instructions inc pointers
mov ax,SCREEN_SEGMENT
mov es,ax ;point ES to display memory for REP STOS
mov si,[bp+HLineListPtr] ;point to the line list
mov ax,[_BitmapWidthInBytes] ;point to the start of the
mul [si+YStart] ; first scan line on which to draw
mov di,ax ;ES:DI points to first scan line to
mov al,dl ; draw; AL is the initial bank #
;upper nibble of AL is read bank #,
mov cl,4 ; lower nibble is write bank # (only
shl dl,cl ; the write bank is really needed for
or al,dl ; this module, but it's less confusing
; to point both to the same place)
mov dx,GC_SEGMENT_SELECT
out dx,al ;set the initial bank
mov dx,di ;ES:DX points to first scan line
mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor
; for the first (top) horizontal line
mov si,[si+Lngth] ;# of scan lines to draw
and si,si ;are there any lines to draw?
jz FillDone ;no, so we're done
mov ax,[bp+Color] ;color with which to fill
mov bp,[_BitmapWidthInBytes] ;so we can keep everything
; in registers inside the loop
;***stack frame pointer destroyed!***
FillLoop:
mov di,[bx+XStart] ;left edge of fill on this line
mov cx,[bx+XEnd] ;right edge of fill
sub cx,di
jl LineFillDone ;skip if negative width
inc cx ;# of pixels to fill on this line
add di,di ;*2 because pixels are 2 bytes in size
add dx,bp ;do we cross a bank during this line?
jnc NormalFill ;no
jz NormalFill ;no
;yes, there is a bank crossing on this
; line; figure out where
sub dx,bp ;point back to start of line
add di,dx ;offset of left edge of fill
jc CrossBankBeforeFilling ;raster splits before the left
; edge of fill
add cx,cx ;fill width in bytes (pixels * 2)
add di,cx ;do we split during the fill area?
jnc CrossBankAfterFilling ;raster splits after the right
jz CrossBankAfterFilling ; edge of fill
;bank boundary falls within fill area;
; draw in two parts, one in each bank
sub di,cx ;point back to start of fill area
neg di ;# of bytes left before split
sub cx,di ;# of bytes to fill to the right of
; the bank split
push cx ;remember right-of-split fill width
mov cx,di ;# of left-of-split bytes to fill
shr cx,1 ;# of left-of-split words to fill
neg di ;offset at which to start filling
rep stosw ;fill left-of-split portion of line
pop cx ;get back right-of-split fill width
shr cx,1 ;# of right-of-split words to fill
;advance to the next bank
INCREMENT_BANK ;point to the next bank (DI already
; points to offset 0, as desired)
rep stosw ;fill right-of-split portion of line
add dx,bp ;point to the next scan line
jmp short CountDownLine ; (already advanced the bank)
;======================================================================
align 2 ;dfill area is entirely to the left of
CrossBankAfterFilling: ; the bank boundary
sub di,cx ;point back to start of fill area
shr cx,1 ;CX = fill width in pixels
jmp short FillAndAdvance ;doesn't split until after the
; fill area, so handle normally
;======================================================================
align 2 ;fill area is entirely to the right of
CrossBankBeforeFilling: ; the bank boundary
INCREMENT_BANK ;first, point to the next bank, where
; the fill area resides
rep stosw ;fill this scan line
add dx,bp ;point to the next scan line
jmp short CountDownLine ; (already advanced the bank)
;======================================================================
align 2 ;no bank boundary problems; just fill
NormalFill: ; normally
sub dx,bp ;point back to start of line
add di,dx ;offset of left edge of fill
FillAndAdvance:
rep stosw ;fill this scan line
LineFillDone:
add dx,bp ;point to the next scan line
jnc CountDownLine ;didn't cross a bank boundary
INCREMENT_BANK ;did cross, so point to the next bank
CountDownLine:
add bx,size HLine ;point to the next line descriptor
dec si ;count off lines to fill
jnz FillLoop
FillDone:
pop di ;restore caller's register variables
pop si
pop bp ;restore caller's stack frame
ret
;======================================================================
_DrawHCLineList endp
end
[LISTING FIVE]
/* Demonstrates unweighted antialiased drawing in 640x480 Hicolor (32K color)
mode. Tested with Borland C++ 2.0 in C mode in the small model. */
#include <conio.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include "polygon.h"
/* Draws the polygon described by the point list PointList in the
color specified by RED, GREEN, AND BLUE, with all vertices
offset by (x,y), to ScanLineBuffer, at ResMul multiple of
horizontal and vertical resolution. The address of ColorTemp is
cast to an int to satisfy the prototype for FillCnvxPolyDrvr; this
trick will work only in a small data model */
#define DRAW_POLYGON_HIGH_RES(PointList,RED,GREEN,BLUE,x,y,ResMul) { \
Polygon.Length = sizeof(PointList)/sizeof(struct Point); \
Polygon.PointPtr = PointTemp; \
/* Multiply all vertical & horizontal coordinates */ \
for (k=0; k<sizeof(PointList)/sizeof(struct Point); k++) { \
PointTemp[k].X = PointList[k].X * ResMul; \
PointTemp[k].Y = PointList[k].Y * ResMul; \
} \
ColorTemp.Red=RED; ColorTemp.Green=GREEN; ColorTemp.Blue=BLUE; \
FillCnvxPolyDrvr(&Polygon, (int)&ColorTemp, x, y, DrawBandedList);}
#define SCREEN_WIDTH 640
#define SCREEN_SEGMENT 0xA000
void main(void);
extern void DrawPixel(int, int, char);
extern void DrawBandedList(struct HLineList *, struct RGB *);
extern int SetHCMode(int);
/* Table of gamma corrected mappings of linear color intensities in
the range 0-255 to the nearest pixel values in the range 0-31,
assuming a gamma of 2.3 */
static unsigned char ColorMappings[] = {
0, 3, 4, 4, 5, 6, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9,10,10,10,
10,10,11,11,11,11,11,12,12,12,12,12,13,13,13,13,13,13,14,14,
14,14,14,14,14,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,
17,17,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,
19,19,19,19,19,19,19,19,20,20,20,20,20,20,20,20,20,20,20,21,
21,21,21,21,21,21,21,21,21,21,22,22,22,22,22,22,22,22,22,22,
22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,
24,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,25,25,25,
25,25,25,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,27,
27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,
28,28,28,28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,
29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,30,30,30,
30,30,30,30,30,30,31,31,31,31,31,31,31,31,31,31};
/* Pointer to buffer in which high-res scanned data will reside */
struct RGB *ScanLineBuffer;
int ScanBandStart, ScanBandEnd; /* top & bottom of each high-res
band we'll draw to ScanLineBuffer */
int ScanBandWidth; /* # subpixels across each scan band */
int BitmapWidthInBytes = 640*2; /* # of bytes per raster line in
Hicolor VGA display memory */
void main()
{
int i, j, k, m, Red, Green, Blue, jXRes, kXWidth;
int SubpixelsPerMegapixel;
unsigned int Megapixel, ResolutionMultiplier;
long BufferSize;
struct RGB ColorTemp;
struct PointListHeader Polygon;
struct Point PointTemp[4];
static struct Point Face0[] =
{{396,276},{422,178},{338,88},{288,178}};
static struct Point Face1[] =
{{306,300},{396,276},{288,178},{210,226}};
static struct Point Face2[] =
{{338,88},{266,146},{210,226},{288,178}};
int LeftBound=210, RightBound=422, TopBound=88, BottomBound=300;
union REGS regset;
printf("Subpixel resolution multiplier:");
scanf("%d", &ResolutionMultiplier);
SubpixelsPerMegapixel = ResolutionMultiplier*ResolutionMultiplier;
ScanBandWidth = SCREEN_WIDTH*ResolutionMultiplier;
/* Get enough space for one scan line scanned out at high
resolution horz and vert (each pixel is 4 bytes) */
if ((BufferSize = (long)ScanBandWidth*4*ResolutionMultiplier) >
0xFFFF) {
printf("Band won't fit in one segment\n"); exit(0); }
if ((ScanLineBuffer = malloc((int)BufferSize)) == NULL) {
printf("Couldn't get memory\n"); exit(0); }
/* Attempt to enable 640x480 Hicolor mode */
if (SetHCMode(0x2E) == 0)
{ printf("No Hicolor DAC detected\n"); exit(0); };
/* Scan out the polygons at high resolution one screen scan line at
a time (ResolutionMultiplier high-res scan lines at a time) */
for (i=TopBound; i<=BottomBound; i++) {
/* Set the band dimensions for this pass */
ScanBandEnd = (ScanBandStart = i*ResolutionMultiplier) +
ResolutionMultiplier - 1;
/* Clear the drawing buffer */
memset(ScanLineBuffer, 0, BufferSize);
/* Draw the current band of the cube to the scan line buffer */
DRAW_POLYGON_HIGH_RES(Face0,0xFF,0,0,0,0,ResolutionMultiplier);
DRAW_POLYGON_HIGH_RES(Face1,0,0xFF,0,0,0,ResolutionMultiplier);
DRAW_POLYGON_HIGH_RES(Face2,0,0,0xFF,0,0,ResolutionMultiplier);
/* Coalesce subpixels into normal screen pixels (megapixels) and draw them */
for (j=LeftBound; j<=RightBound; j++) {
jXRes = j*ResolutionMultiplier;
/* For each screen pixel, sum all the corresponding
subpixels, for each color component */
for (k=Red=Green=Blue=0; k<ResolutionMultiplier; k++) {
kXWidth = k*ScanBandWidth;
for (m=0; m<ResolutionMultiplier; m++) {
Red += ScanLineBuffer[jXRes+kXWidth+m].Red;
Green += ScanLineBuffer[jXRes+kXWidth+m].Green;
Blue += ScanLineBuffer[jXRes+kXWidth+m].Blue;
}
}
/* Calc each color component's average brightness; convert
that into a gamma corrected portion of a Hicolor pixel,
then combine the colors into one Hicolor pixel */
Red = ColorMappings[Red/SubpixelsPerMegapixel];
Green = ColorMappings[Green/SubpixelsPerMegapixel];
Blue = ColorMappings[Blue/SubpixelsPerMegapixel];
Megapixel = (Red << 10) + (Green << 5) + Blue;
DrawPixel(j, i, Megapixel);
}
}
getch(); /* wait for a keypress */
/* Return to text mode and exit */
regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */
int86(0x10, ®set, ®set);
}
[LISTING SIX]
/* Draws pixels from the list of horizontal lines passed in, to a 32-bpp
buffer; drawing takes place only for scan lines between ScanBandStart and
ScanBandEnd, inclusive; drawing goes to ScanLineBuffer, with the scan line at
ScanBandStart mapping to the first scan line in ScanLineBuffer. Note that
Color here points to an RGB structure that maps directly to the buffer's pixel
format, rather than containing a 16-bit integer. Tested with Borland C++ 2.0
in C mode in the small model */
#include "polygon.h"
extern struct RGB *ScanLineBuffer; /* drawing goes here */
extern int ScanBandStart, ScanBandEnd; /* limits of band to draw */
extern int ScanBandWidth; /* # of subpixels across scan band */
void DrawBandedList(struct HLineList * HLineListPtr,
struct RGB *Color)
{
struct HLine *HLinePtr;
int Length, Width, YStart = HLineListPtr->YStart, i;
struct RGB *BufferPtr, *WorkingBufferPtr;
/* Done if fully off the bottom or top of the band */
if (YStart > ScanBandEnd) return;
Length = HLineListPtr->Length;
if ((YStart + Length) <= ScanBandStart) return;
/* Point to XStart/XEnd descriptor for the first (top) horizontal line */
HLinePtr = HLineListPtr->HLinePtr;
/* Confine drawing to the specified band */
if (YStart < ScanBandStart) {
/* Skip ahead to the start of the band */
Length -= ScanBandStart - YStart;
HLinePtr += ScanBandStart - YStart;
YStart = ScanBandStart;
}
if (Length > (ScanBandEnd - YStart + 1))
Length = ScanBandEnd - YStart + 1;
/* Point to the start of the first scan line on which to draw */
BufferPtr = ScanLineBuffer + (YStart-ScanBandStart)*ScanBandWidth;
/* Draw each horizontal line within the band in turn, starting with
the top one and advancing one line each time */
while (Length-- > 0) {
/* Fill whole horiz line with Color if it has positive width */
if ((Width = HLinePtr->XEnd - HLinePtr->XStart + 1) > 0) {
WorkingBufferPtr = BufferPtr + HLinePtr->XStart;
for (i = 0; i < Width; i++) *WorkingBufferPtr++ = *Color;
}
HLinePtr++; /* point to next scan line X info */
BufferPtr += ScanBandWidth; /* point to start of next line */
}
}
[LISTING SEVEN]
/* POLYGON.H: Header file for polygon-filling code */
/* Describes a single point (used for a single vertex) */
struct Point {
int X; /* X coordinate */
int Y; /* Y coordinate */
};
/* Describes a series of points (used to store a list of vertices that
describe a polygon; each vertex is assumed to connect to the two adjacent
vertices, and the last vertex is assumed to connect to the first) */
struct PointListHeader {
int Length; /* # of points */
struct Point * PointPtr; /* pointer to list of points */
};
/* Describes the beginning and ending X coordinates of a single
horizontal line */
struct HLine {
int XStart; /* X coordinate of leftmost pixel in line */
int XEnd; /* X coordinate of rightmost pixel in line */
};
/* Describes a Length-long series of horizontal lines, all assumed to be on
contiguous scan lines starting at YStart and proceeding downward (used to
describe scan-converted polygon to low-level hardware-dependent drawing code)*/
struct HLineList {
int Length; /* # of horizontal lines */
int YStart; /* Y coordinate of topmost line */
struct HLine * HLinePtr; /* pointer to list of horz lines */
};
/* Describes a color as an RGB triple, plus one byte for other info */
struct RGB { unsigned char Red, Green, Blue, Spare; };